package com.kunlun.common.utils;

import com.kunlun.common.model.monitor.CpuInfoModel;
import com.kunlun.common.model.monitor.DiskInfoModel;
import com.kunlun.common.model.monitor.JvmInfoModel;
import com.kunlun.common.model.monitor.MemoryInfoModel;
import com.kunlun.common.model.monitor.NetworkInfoModel;
import com.kunlun.common.model.monitor.SystemDataModel;
import com.kunlun.common.model.monitor.SystemInfoModel;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import oshi.SystemInfo;
import oshi.hardware.CentralProcessor;
import oshi.hardware.GlobalMemory;
import oshi.hardware.HardwareAbstractionLayer;
import oshi.hardware.NetworkIF;
import oshi.software.os.FileSystem;
import oshi.software.os.OSFileStore;
import oshi.software.os.OperatingSystem;
import oshi.util.Util;

import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * 监控工具
 */
@Component
public class MonitorUtil {

    private static Logger log = LogManager.getLogger();

    private SystemInfo systemInfo = new SystemInfo();

    @Value("${spring.application.name}")
    private String applicationName;

    public SystemDataModel monitor() {
        log.info("MonitorUtil applicationName => " + applicationName);
        SystemInfoModel sysInfo = getSysInfo();
        log.info("MonitorUtil sysInfo => " + JsonUtil.toJsonString(sysInfo));
        CpuInfoModel cpuInfo = getCpuInfo();
        log.info("MonitorUtil cpuInfo => " + JsonUtil.toJsonString(cpuInfo));
        MemoryInfoModel memoryInfo = getMemoryInfo();
        log.info("MonitorUtil memoryInfo => " + JsonUtil.toJsonString(memoryInfo));
        JvmInfoModel jvmInfo = getJvmInfo();
        log.info("MonitorUtil jvmInfo => " + JsonUtil.toJsonString(jvmInfo));
        DiskInfoModel diskInfo = getDiskInfo();
        log.info("MonitorUtil diskInfos => " + JsonUtil.toJsonString(diskInfo));
        NetworkInfoModel netIoInfo = getNetIoInfo();
        log.info("MonitorUtil netIoInfo => " + JsonUtil.toJsonString(netIoInfo));
        return new SystemDataModel(applicationName, sysInfo, cpuInfo, memoryInfo, diskInfo, jvmInfo, netIoInfo);
    }

    /**
     * 获取系统信息
     *
     * @return {@link SystemInfoModel}
     */
    public SystemInfoModel getSysInfo() {
        Properties props = System.getProperties();
        SystemInfoModel sysInfo = new SystemInfoModel();
        InetAddress inetAddress = null;
        try {
            inetAddress = InetAddress.getLocalHost();
            sysInfo.setName(inetAddress.getHostName());
            sysInfo.setIp(inetAddress.getHostAddress());
        } catch (UnknownHostException e) {
            sysInfo.setName("unknown");
            sysInfo.setIp("unknown");
        }
        sysInfo.setOsName(props.getProperty("os.name"));
        sysInfo.setOsArch(props.getProperty("os.arch"));
        sysInfo.setUserDir(props.getProperty("user.dir"));
        return sysInfo;
    }

    /**
     * 获取 cpu 信息
     *
     * @return {@link CpuInfoModel}
     */
    public CpuInfoModel getCpuInfo() {
        HardwareAbstractionLayer hardware = systemInfo.getHardware();
        CentralProcessor centralProcessor = hardware.getProcessor();
        long[] prevTicks = centralProcessor.getSystemCpuLoadTicks();
        Util.sleep(600);
        long[] ticks = centralProcessor.getSystemCpuLoadTicks();
        long nice = ticks[CentralProcessor.TickType.NICE.getIndex()] - prevTicks[CentralProcessor.TickType.NICE.getIndex()];
        long irq = ticks[CentralProcessor.TickType.IRQ.getIndex()] - prevTicks[CentralProcessor.TickType.IRQ.getIndex()];
        long softirq = ticks[CentralProcessor.TickType.SOFTIRQ.getIndex()] - prevTicks[CentralProcessor.TickType.SOFTIRQ.getIndex()];
        long steal = ticks[CentralProcessor.TickType.STEAL.getIndex()] - prevTicks[CentralProcessor.TickType.STEAL.getIndex()];
        long sys = ticks[CentralProcessor.TickType.SYSTEM.getIndex()] - prevTicks[CentralProcessor.TickType.SYSTEM.getIndex()];
        long user = ticks[CentralProcessor.TickType.USER.getIndex()] - prevTicks[CentralProcessor.TickType.USER.getIndex()];
        long ioWait = ticks[CentralProcessor.TickType.IOWAIT.getIndex()] - prevTicks[CentralProcessor.TickType.IOWAIT.getIndex()];
        long idle = ticks[CentralProcessor.TickType.IDLE.getIndex()] - prevTicks[CentralProcessor.TickType.IDLE.getIndex()];
        long totalCpu = user + nice + sys + idle + ioWait + irq + softirq + steal;
        String totalCPU = String.valueOf(totalCpu);
        CpuInfoModel cpuInfo = new CpuInfoModel();
        cpuInfo.setPhysicalProcessorCount(centralProcessor.getPhysicalProcessorCount());
        cpuInfo.setLogicalProcessorCount(centralProcessor.getLogicalProcessorCount());
        cpuInfo.setSystemPercent(CalculateUtil.calculate("a / b", 2, String.valueOf(sys), totalCPU));
        cpuInfo.setUserPercent(CalculateUtil.calculate("a / b", 2, String.valueOf(user), totalCPU));
        cpuInfo.setWaitPercent(CalculateUtil.calculate("a / b", 2, String.valueOf(ioWait), totalCPU));
        cpuInfo.setUsePercent(CalculateUtil.calculate("a - b / c", 2, "1", String.valueOf(idle), totalCPU));
        return cpuInfo;
    }

    /**
     * 获取内存使用信息
     *
     * @return {@link MemoryInfoModel}
     */
    public MemoryInfoModel getMemoryInfo() {
        HardwareAbstractionLayer hardware = systemInfo.getHardware();
        GlobalMemory globalMemory = hardware.getMemory();
        long totalByte = globalMemory.getTotal();
        long availableByte = globalMemory.getAvailable();
        MemoryInfoModel memoryInfo = new MemoryInfoModel();
        memoryInfo.setTotal(formatByte(totalByte));
        memoryInfo.setUsed(formatByte(totalByte - availableByte));
        memoryInfo.setFree(formatByte(availableByte));
        memoryInfo.setUsePercent(CalculateUtil.calculate("(a - b) / c", 2, String.valueOf(totalByte), String.valueOf(availableByte), String.valueOf(totalByte)));
        return memoryInfo;
    }

    /**
     * 获取 JVM 信息
     *
     * @return {@link JvmInfoModel}
     */
    public JvmInfoModel getJvmInfo() {
        Properties props = System.getProperties();
        Runtime runtime = Runtime.getRuntime();
        long jvmTotalMemoryByte = runtime.totalMemory();
        long freeMemoryByte = runtime.freeMemory();
        JvmInfoModel jvmInfo = new JvmInfoModel();
        jvmInfo.setJdkVersion(props.getProperty("java.version"));
        jvmInfo.setJdkHome(props.getProperty("java.home"));
        jvmInfo.setJvmTotalMemory(formatByte(jvmTotalMemoryByte));
        jvmInfo.setMaxMemory(formatByte(runtime.maxMemory()));
        jvmInfo.setUsedMemory(formatByte(jvmTotalMemoryByte - freeMemoryByte));
        jvmInfo.setFreeMemory(formatByte(freeMemoryByte));
        jvmInfo.setUsePercent(CalculateUtil.calculate("(a - b) / c", 2, String.valueOf(jvmTotalMemoryByte), String.valueOf(freeMemoryByte), String.valueOf(jvmTotalMemoryByte)));
        RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
        jvmInfo.setJdkName(runtimeMXBean.getVmName());
        jvmInfo.setStartTime(runtimeMXBean.getStartTime());
        jvmInfo.setUptime(runtimeMXBean.getUptime());
        return jvmInfo;
    }

    /**
     * 获取磁盘使用信息
     */
    public List<DiskInfoModel> getDiskInfos() {
        OperatingSystem operatingSystem = systemInfo.getOperatingSystem();
        FileSystem fileSystem = operatingSystem.getFileSystem();
        List<DiskInfoModel> diskInfos = new ArrayList<>();
        List<OSFileStore> fsArray = fileSystem.getFileStores();
        for (OSFileStore fs : fsArray) {
            DiskInfoModel diskInfo = new DiskInfoModel();
            diskInfo.setName(fs.getName());
            diskInfo.setVolume(fs.getVolume());
            diskInfo.setLogicalVolume(fs.getLogicalVolume());
            diskInfo.setMount(fs.getMount());
            diskInfo.setDescription(fs.getDescription());
            diskInfo.setOptions(fs.getOptions());
            diskInfo.setType(fs.getType());
            diskInfo.setUUID(fs.getUUID());
            long usable = fs.getUsableSpace();
            diskInfo.setUsableSpace(usable);
            long total = fs.getTotalSpace();
            diskInfo.setSize(formatByte(total));
            diskInfo.setTotalSpace(total);
            diskInfo.setAvail(formatByte(usable));
            String usedSize = CalculateUtil.calculate("a - b", 0, String.valueOf(total), String.valueOf(usable));
            diskInfo.setUsed(formatByte(Long.valueOf(usedSize)));
            String usePercent = "0";
            if (total > 0) {
                usePercent = CalculateUtil.calculate("a / b * c", 2, usedSize, String.valueOf(total), "100");
            }
            diskInfo.setUsePercent(usePercent);
            diskInfos.add(diskInfo);
        }
        return diskInfos;
    }

    /**
     * 获取磁盘使用信息
     */
    public DiskInfoModel getDiskInfo() {
        OperatingSystem operatingSystem = systemInfo.getOperatingSystem();
        FileSystem fileSystem = operatingSystem.getFileSystem();
        List<OSFileStore> fsArray = fileSystem.getFileStores();
        String typeDisk = null, totalDisk = null, usableDisk = null, availDisk = null;
        for (OSFileStore fs : fsArray) {
            typeDisk = fs.getType();
            totalDisk = CalculateUtil.calculate("a + b", 0, totalDisk, String.valueOf(fs.getTotalSpace()));
            usableDisk = CalculateUtil.calculate("a + b", 0, usableDisk, String.valueOf(fs.getUsableSpace()));
            availDisk = CalculateUtil.calculate("a + b", 0, availDisk, String.valueOf(fs.getUsableSpace()));
        }

        DiskInfoModel diskInfo = new DiskInfoModel();
        diskInfo.setType(typeDisk);
        diskInfo.setTotalSpace(Long.valueOf(totalDisk));
        diskInfo.setAvail(availDisk);
        String usedSize = CalculateUtil.calculate("a - b", 0, String.valueOf(totalDisk), String.valueOf(usableDisk));
        String usePercent = CalculateUtil.calculate("a / b * c", 2, usedSize, String.valueOf(totalDisk), "100");
        diskInfo.setUsePercent(usePercent);
        return diskInfo;
    }

    /**
     * 获取网络带宽信息
     *
     * @return {@link NetworkInfoModel}
     * @throws Exception
     */
    public NetworkInfoModel getNetIoInfo() {
        long rxBytesBegin = 0;
        long txBytesBegin = 0;
        long rxPacketsBegin = 0;
        long txPacketsBegin = 0;
        long rxBytesEnd = 0;
        long txBytesEnd = 0;
        long rxPacketsEnd = 0;
        long txPacketsEnd = 0;
        HardwareAbstractionLayer hal = systemInfo.getHardware();
        List<NetworkIF> listBegin = hal.getNetworkIFs();
        for (NetworkIF net : listBegin) {
            rxBytesBegin += net.getBytesRecv();
            txBytesBegin += net.getBytesSent();
            rxPacketsBegin += net.getPacketsRecv();
            txPacketsBegin += net.getPacketsSent();
        }

        //暂停3秒
        Util.sleep(3000);

        List<NetworkIF> listEnd = hal.getNetworkIFs();
        for (NetworkIF net : listEnd) {
            rxBytesEnd += net.getBytesRecv();
            txBytesEnd += net.getBytesSent();
            rxPacketsEnd += net.getPacketsRecv();
            txPacketsEnd += net.getPacketsSent();
        }

        long rxBytesAvg = (rxBytesEnd - rxBytesBegin) / 3 / 1024;
        long txBytesAvg = (txBytesEnd - txBytesBegin) / 3 / 1024;
        long rxPacketsAvg = (rxPacketsEnd - rxPacketsBegin) / 3 / 1024;
        long txPacketsAvg = (txPacketsEnd - txPacketsBegin) / 3 / 1024;
        NetworkInfoModel netIoInfo = new NetworkInfoModel();
        netIoInfo.setRxbyt(rxBytesAvg + "");
        netIoInfo.setTxbyt(txBytesAvg + "");
        netIoInfo.setRxpck(rxPacketsAvg + "");
        netIoInfo.setTxpck(txPacketsAvg + "");
        return netIoInfo;
    }

    public String formatByte(long byteNumber) {
        //换算单位
        double FORMAT = 1024.0;
        double kbNumber = byteNumber / FORMAT;
        if (kbNumber < FORMAT) {
            return decimalFormat("#.##KB", kbNumber);
        }
        double mbNumber = kbNumber / FORMAT;
        if (mbNumber < FORMAT) {
            return decimalFormat("#.##MB", mbNumber);
        }
        double gbNumber = mbNumber / FORMAT;
        if (gbNumber < FORMAT) {
            return decimalFormat("#.##GB", gbNumber);
        }
        return decimalFormat("#.##TB", gbNumber / FORMAT);
    }

    public String decimalFormat(String pattern, double number) {
        return new DecimalFormat(pattern).format(number);
    }
}
